#!/usr/bin/env zx
$.shell = "powershell";
$.verbose = true; // 生产环境关闭详细日志，调试时设为true

// ===================== 配置区（核心参数，根据实际情况修改）=====================
const CONFIG = {
  // 本地配置文件路径
  configPath: path.resolve(__dirname, "config.json"),
  // SSH连接信息（优先从config.json读取）
  ssh: {
    privateKeyPath: "C:/Users/XIALEI/.ssh/id_rsa", // 本地私钥正确路径
    port: 22,
  },
  targetDir: "C:/Users/Administrator/koa-file-server",
  git: {
    repoUrl: "git@gitcode.com:xialei520/koa-file-server.git",
    branch: "main",
  },
  // 服务管理（根据项目启动方式修改）
  service: {
    name: "koa-file-server", // 服务名称（用于pm2或系统服务）
    startCmd: "npm start", // 启动命令
    stopCmd: "npm stop", // 停止命令（如果有）
  },
  // 其他配置
  backup: {
    enable: true, // 是否开启备份
    dir: "C:/Users/Administrator/backups", // 备份存放目录
  },
  retryCount: 2, // 重试次数
  timeout: 30000, // 命令超时时间（毫秒）
};

// ===================== 工具函数 =====================
/**
 * 带重试和超时的命令执行
 */
async function execWithRetry(fn, retry = CONFIG.retryCount) {
  try {
    const result = await Promise.race([
      fn(), // 直接执行函数，不添加 bash 选项
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error("命令执行超时")), CONFIG.timeout)
      ),
    ]);
    return result;
  } catch (err) {
    if (retry > 0) {
      console.log(`⚠️ 执行失败，剩余重试次数：${retry}，错误：${err.message}`);
      await sleep(2000);
      return execWithRetry(fn, retry - 1);
    }
    throw new Error(`❌ 重试耗尽，原错误：${err.message}`);
  }
}

/**
 * 检查本地文件是否存在
 */
async function checkLocalFileExists(path) {
  try {
    await fs.access(path);
    return true;
  } catch {
    return false;
  }
}

/**
 * 构建SSH连接命令前缀（兼容密钥/密码登录）
 */
async function getSshPrefix(serverConfig) {
  const { host, username, port = CONFIG.ssh.port } = serverConfig;
  if (await checkLocalFileExists(CONFIG.ssh.privateKeyPath)) {
    // 私钥路径用双引号包裹（PowerShell 路径含空格时需处理）
    return `ssh -o StrictHostKeyChecking=no -p ${port} -i "${CONFIG.ssh.privateKeyPath}" ${username}@${host}`;
  }
  // 密码登录（不推荐，仅应急用，需服务器开启密码登录）
  if (password) {
    // Windows下需安装sshpass（建议通过Chocolatey安装：choco install sshpass）
    return `sshpass -p '${password}' ssh -o StrictHostKeyChecking=no -p ${port} ${username}@${host}`;
  }
  throw new Error("❌ 未配置有效的SSH登录方式（密钥或密码）");
}

/**
 * 远程执行命令（返回结果）
 */
async function execRemote(serverConfig, cmd) {
  const sshPrefix = await getSshPrefix(serverConfig);
  // 1. 移除 bash 特有的 set -euo pipefail（PowerShell 不支持）
  // 2. 用 PowerShell 兼容的引号转义（双引号内用 ` 转义，而非 \）
  const escapedCmd = cmd.replace(/"/g, '`"').replace(/'/g, "`'");
  // 3. 直接拼接命令，不使用 $'...' 语法
  const fullCmd = `${sshPrefix} "${escapedCmd}"`;
  console.log(`📝 执行远程命令：${cmd}`);
  // 4. 执行命令时不包裹 bash 选项
  return await execWithRetry(() => $`${fullCmd}`);
}

// ===================== 核心逻辑 =====================
(async () => {
  try {
    // 1. 加载配置文件
    if (!(await checkLocalFileExists(CONFIG.configPath))) {
      throw new Error(`配置文件不存在：${CONFIG.configPath}`);
    }
    const config = JSON.parse(await fs.readFile(CONFIG.configPath));
    const serverConfig = config.server; // { host, username, password }
    if (!serverConfig?.host || !serverConfig?.username) {
      throw new Error("配置文件缺少服务器信息（host/username）");
    }

    // 2. 前置检查：服务器连通性
    console.log(
      `[1/7] 检查服务器连通性：${serverConfig.host}:${CONFIG.ssh.port}`
    );
    await execRemote(serverConfig, "echo '连接成功'");

    // 3. 远程目录初始化（确保目标目录和备份目录存在）
    console.log(`[2/7] 初始化远程目录`);
    await execRemote(
      serverConfig,
      [
        `if (-not (Test-Path '${CONFIG.targetDir}')) { New-Item -ItemType Directory -Path '${CONFIG.targetDir}' | Out-Null }`,
        `if (-not (Test-Path '${CONFIG.backup.dir}')) { New-Item -ItemType Directory -Path '${CONFIG.backup.dir}' | Out-Null }`,
      ].join("; ")
    );

    // 4. 备份现有项目（如果开启）
    if (CONFIG.backup.enable) {
      console.log(`[3/7] 备份现有项目`);
      const backupName = `backup_${new Date()
        .toISOString()
        .replace(/:/g, "-")}`;
      await execRemote(
        serverConfig,
        [
          `if (Test-Path '${CONFIG.targetDir}/.git') {`, // 仅当目录是git仓库时备份
          `Compress-Archive -Path '${CONFIG.targetDir}/*' -DestinationPath '${CONFIG.backup.dir}/${backupName}.zip' -Force`,
          `Write-Host '备份成功：${backupName}.zip'`,
          `}`,
        ].join("; ")
      );
    }

    // 5. 拉取代码（clone或pull）
    console.log(`[4/7] 拉取代码（分支：${CONFIG.git.branch}）`);
    await execRemote(
      serverConfig,
      [
        `Set-Location '${CONFIG.targetDir}'`,
        `if (Test-Path '.git') {`,
        `  git fetch --all`,
        `  git reset --hard origin/${CONFIG.git.branch}`, // 强制同步远程分支（避免本地修改冲突）
        `  git pull origin ${CONFIG.git.branch}`,
        `} else {`,
        `  git clone -b ${CONFIG.git.branch} ${CONFIG.git.repoUrl} .`, // 注意末尾的.表示克隆到当前目录
        `}`,
      ].join("; ")
    );

    // 6. 安装依赖（仅生产环境依赖）
    console.log(`[5/7] 安装项目依赖`);
    await execRemote(
      serverConfig,
      [
        `Set-Location '${CONFIG.targetDir}'`,
        `npm install --production`, // 跳过devDependencies，加快速度
      ].join("; ")
    );

    // 7. 重启服务（根据实际启动方式修改）
    console.log(`[6/7] 重启项目服务`);
    await execRemote(
      serverConfig,
      [
        `Set-Location '${CONFIG.targetDir}'`,
        // 停止现有服务（如果用pm2管理）
        `if (pm2 describe ${CONFIG.service.name} 2>$null) { pm2 stop ${CONFIG.service.name} }`,
        // 启动服务（示例：用pm2启动，若用其他方式可修改）
        `pm2 start ${CONFIG.service.startCmd} --name ${CONFIG.service.name}`,
        `pm2 save`, // 保存当前进程列表
      ].join("; ")
    );

    // 8. 验证服务状态
    console.log(`[7/7] 验证服务状态`);
    const statusResult = await execRemote(
      serverConfig,
      `pm2 show ${CONFIG.service.name}`
    );
    if (!statusResult.stdout.includes("online")) {
      throw new Error("服务启动失败，状态非online");
    }

    console.log("🎉 项目发布成功！");
    process.exit(0);
  } catch (err) {
    console.error("❌ 发布失败：", err.message);
    console.error("错误详情：", err.stderr || err.stack);
    process.exit(1);
  }
})();
